package com.che.software.testato.business;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;

import com.che.software.testato.domain.entity.Element;
import com.che.software.testato.domain.entity.ProceduralArrow;
import com.che.software.testato.domain.entity.Transition;
import com.che.software.testato.domain.entity.creation.VariantCreation;
import com.che.software.testato.domain.enumeration.PointType;
import com.che.software.testato.domain.enumeration.VariantTypes;

import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
import edu.uci.ics.jung.graph.util.EdgeType;

/**
 * Manager dedicated to the procedural diagrams.
 * 
 * @author Clement HELIOU (clement.heliou@che-software.com).
 * @copyright Che Software.
 * @license GNU General Public License.
 * @since July, 2011.
 * 
 *        This file is part of Testato.
 * 
 *        Testato is free software: you can redistribute it and/or modify it
 *        under the terms of the GNU General Public License as published by the
 *        Free Software Foundation, either version 3 of the License, or (at your
 *        option) any later version.
 * 
 *        Testato is distributed in the hope that it will be useful, but WITHOUT
 *        ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *        FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *        for more details.
 * 
 *        You should have received a copy of the GNU General Public License
 *        along with Testato. If not, see <http://www.gnu.org/licenses/>.
 * 
 *        Testato's logo is a creation of Arrioch
 *        (http://arrioch.deviantart.com/) and it's distributed under the terms
 *        of the Creative Commons License.
 */
@Service("proceduralDiagramManager")
public class ProceduralDiagramManager {

	/**
	 * Constants.
	 */
	private static final Logger LOGGER = Logger.getLogger(ProceduralDiagramManager.class);

	/**
	 * Members.
	 */
	private List<VariantCreation> generatedVariants;

	/**
	 * Creates a procedural diagram from elements and transitions lists.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param elements the elements list.
	 * @param transitions the transitions list.
	 * @return the resulting procedural diagram.
	 * @since July, 2011.
	 */
	public DirectedGraph<Element, ProceduralArrow> createProceduralDiagramFromElementsAndTransitions(List<Element> elements, List<Transition> transitions) {
		LOGGER.debug("createProceduralDiagramFromElementsAndTransitions(" + elements.size() + " elements, " + transitions.size() + " transitions).");
		DirectedGraph<Element, ProceduralArrow> proceduralDiagram = new DirectedSparseMultigraph<Element, ProceduralArrow>();
		for (Transition transition : transitions) {
			proceduralDiagram.addEdge(new ProceduralArrow(transition.getLabel(), transition.getTransitionId()), getElementFromList(elements, transition.getSourceElementId()), getElementFromList(elements, transition.getTargetElementId()), EdgeType.DIRECTED);
		}
		LOGGER.debug(proceduralDiagram);
		return proceduralDiagram;
	}

	/**
	 * Generates all the variants contained in a given procedural diagram using
	 * a recursive algorithm.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param proceduralDiagram the procedural diagram containing the variants
	 *        to discover.
	 * @return the resulting object list.
	 * @since July, 2011.
	 */
	public List<VariantCreation> generateVariantsFromProceduralDiagram(DirectedGraph<Element, ProceduralArrow> proceduralDiagram) {
		LOGGER.debug("generateVariantsFromProceduralDiagram(" + proceduralDiagram + ").");
		generatedVariants = new ArrayList<VariantCreation>();
		getPath(getStartElementFromProceduralDiagram(proceduralDiagram), proceduralDiagram, null);
		LOGGER.debug(generatedVariants.size() + " variants have been found.");
		return generatedVariants;
	}

	/**
	 * Retrieves an element contained in a list from his id.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param elements the elements list.
	 * @param elementId the element to retrieve id.
	 * @return the resulting object.
	 * @since July, 2011.
	 */
	private Element getElementFromList(List<Element> elements, int elementId) {
		LOGGER.debug("getElementFromList(" + elementId + ").");
		for (Element element : elements) {
			if (elementId == element.getElementId()) {
				return element;
			}
		}
		return null;
	}

	/**
	 * Method which is used in the recursive algorithm to discover all the
	 * variants contained in a procedural diagram. The current element is the
	 * one to inspect in the next iteration. Typically, the work is the
	 * following. The element type is detected then related actions are done. To
	 * finalize the iteration, the method calls himself with the next element to
	 * inspect as parameter. The stop condition is reached with an end point
	 * whatever his type.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param currentElement
	 * @param proceduralDiagram
	 * @param currentVariant
	 * @since July, 2011.
	 */
	private void getPath(Element currentElement, DirectedGraph<Element, ProceduralArrow> proceduralDiagram, VariantCreation currentVariant) {
		LOGGER.debug("getPath(" + currentElement.getLabel() + ", variant[" + ((null != currentVariant) ? currentVariant.getElements().size() : 0) + " elements, " + ((null != currentVariant) ? currentVariant.getTransitions().size() : 0) + " transitions]).");
		if (0 != currentElement.getActivityId()) { // Activity case.
			Element followingElement = new ArrayList<Element>(proceduralDiagram.getSuccessors(currentElement)).get(0);
			currentVariant.addElement(followingElement); // Following element.
			currentVariant.addTransition(proceduralDiagram.findEdge(currentElement, followingElement));
			getPath(followingElement, proceduralDiagram, currentVariant);
		} else { // Points cases.
			Element followingElement = null;
			switch (currentElement.getPointType()) { // Never default case.
			case CHOICE:
				List<Element> followingElements = new ArrayList<Element>(proceduralDiagram.getSuccessors(currentElement));
				for (int i = 1; i < followingElements.size(); i++) {
					Element following = followingElements.get(i);
					VariantCreation clone = new VariantCreation(currentVariant);
					clone.addElement(following);
					clone.addTransition(proceduralDiagram.findEdge(currentElement, following));
					getPath(following, proceduralDiagram, clone);
					generatedVariants.add(clone);
				}
				Element firstFollowingElement = followingElements.get(0);
				currentVariant.addElement(firstFollowingElement);
				currentVariant.addTransition(proceduralDiagram.findEdge(currentElement, firstFollowingElement));
				getPath(firstFollowingElement, proceduralDiagram, currentVariant);
				break;
			case JOIN:
				followingElement = new ArrayList<Element>(proceduralDiagram.getSuccessors(currentElement)).get(0);
				currentVariant.addElement(followingElement);
				currentVariant.addTransition(proceduralDiagram.findEdge(currentElement, followingElement));
				getPath(followingElement, proceduralDiagram, currentVariant);
				break;
			case NEGATIVE_END:
				currentVariant.setVariantType(VariantTypes.FAILURE);
				LOGGER.debug("This variant is a failure [" + currentVariant.getElements().size() + " elements, " + currentVariant.getTransitions().size() + " tranistions]!");
				break;
			case POSITIVE_END:
				currentVariant.setVariantType(VariantTypes.SUCCESS);
				LOGGER.debug("This variant is a success [" + currentVariant.getElements().size() + " elements, " + currentVariant.getTransitions().size() + " tranistions]!");
				break;
			case START:
				VariantCreation firstVariant = new VariantCreation();
				firstVariant.addElement(currentElement); // Start element.
				followingElement = new ArrayList<Element>(proceduralDiagram.getSuccessors(currentElement)).get(0);
				firstVariant.addElement(followingElement); // Following element.
				firstVariant.addTransition(proceduralDiagram.findEdge(currentElement, followingElement));
				getPath(followingElement, proceduralDiagram, firstVariant);
				generatedVariants.add(firstVariant);
				break;
			}
		}
	}

	/**
	 * Retrieves the start element of a procedural diagram.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param proceduralDiagram the procedural diagram to inspect.
	 * @return the start element of this procedural diagram.
	 * @since July, 2011.
	 */
	private Element getStartElementFromProceduralDiagram(DirectedGraph<Element, ProceduralArrow> proceduralDiagram) {
		LOGGER.debug("getStartElementFromProceduralDiagram(" + proceduralDiagram + ").");
		for (Element element : proceduralDiagram.getVertices()) {
			if (null != element.getPointType() && element.getPointType().equals(PointType.START)) {
				return element;
			}
		}
		return null; // Doesn't happened.
	}

	/**
	 * Getter for the private field value generatedVariants.
	 * 
	 * @return the generatedVariants field value.
	 */
	public List<VariantCreation> getGeneratedVariants() {
		return generatedVariants;
	}

	/**
	 * Setting a value to the generatedVariants field.
	 * 
	 * @param generatedVariants the value to set.
	 */
	public void setGeneratedVariants(List<VariantCreation> generatedVariants) {
		this.generatedVariants = generatedVariants;
	}
}